/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.io.xml.crn;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.stax.StAXSource;
import org.apereo.portal.io.xml.IDataImporter;
import org.apereo.portal.io.xml.PortalDataKey;
import org.apereo.portal.utils.Tuple;
import org.apereo.portal.utils.threading.NoopLock;
import org.codehaus.staxmate.dom.DOMConverter;
import org.dom4j.Element;
import org.dom4j.io.DOMReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.oxm.Unmarshaller;
import org.springframework.oxm.XmlMappingException;
import org.springframework.util.xml.FixedXMLEventStreamReader;
import org.w3c.dom.Document;

/** Generic dom4j import impl */
public abstract class AbstractDom4jImporter
        implements IDataImporter<Tuple<String, Element>>, Unmarshaller {
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private Set<PortalDataKey> dataKeys;
    private Lock lock = NoopLock.INSTANCE;

    public void setDataKeys(Set<PortalDataKey> dataKeys) {
        this.dataKeys = dataKeys;
    }

    /** Set if the import operation is thread-safe, defaults to true */
    public void setThreadSafe(boolean threadSafe) {
        if (threadSafe) {
            this.lock = NoopLock.INSTANCE;
        } else {
            this.lock = new ReentrantLock();
        }
    }

    @Override
    public Set<PortalDataKey> getImportDataKeys() {
        return this.dataKeys;
    }

    @Override
    public void importData(Tuple<String, Element> data) {
        // Have to make local reference since importLock is NOT immutable
        final Lock lock = this.lock;
        lock.lock();
        try {
            this.importDataElement(data);
        } finally {
            lock.unlock();
        }
    }

    protected abstract void importDataElement(Tuple<String, Element> data);

    @Override
    public Unmarshaller getUnmarshaller() {
        return this;
    }

    // ** Unmarshaller APIs **/

    @Override
    public boolean supports(Class<?> clazz) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Tuple<String, Element> unmarshal(Source source) throws IOException, XmlMappingException {
        // Convert the StAX XMLEventReader to a dom4j Element
        final Element node = convertToElement(source);
        return new Tuple<String, Element>(source.getSystemId(), node);
    }

    protected Element convertToElement(Source source) {
        if (source instanceof StAXSource) {
            final StAXSource staxSource = (StAXSource) source;

            final DOMConverter domConverter = new DOMConverter();
            final Document document;
            try {
                XMLStreamReader xmlStreamReader = staxSource.getXMLStreamReader();
                if (xmlStreamReader == null) {
                    final XMLEventReader xmlEventReader = staxSource.getXMLEventReader();
                    xmlStreamReader = new FixedXMLEventStreamReader(xmlEventReader);
                }

                document = domConverter.buildDocument(xmlStreamReader);
            } catch (XMLStreamException e) {
                throw new RuntimeException("Failed to parse StAX Reader into Dom4J Element", e);
            }
            final DOMReader domReader = new DOMReader();
            final org.dom4j.Document dom4JDocument = domReader.read(document);
            dom4JDocument.setName(source.getSystemId());
            return dom4JDocument.getRootElement();
        }

        throw new IllegalArgumentException(
                "Source of type " + source.getClass() + " is not supported");
    }
}
